#pragma once

#include "zen_vap_helper.h"
#include "zen_image_coder.h"
#include "zen_cache.h"
#include <map>

namespace Zen { namespace Vap {
#if ZEN_APP_GLES
	typedef GL::Texture2D TextureBuffer;
#elif ZEN_APP_METAL
	typedef Metal::Texture TextureBuffer;
#endif

	/*!
	 @class Texture
	 @abstract image texture.
	 */
	class Texture {
	public:
		Texture() {printf("texture new\n");}
		virtual ~Texture() { printf("texture delete\n"); }
		Texture(Texture&) = delete;
	public:
		TextureBuffer & buffer() { return m_buffer; }

		/*!
		 @function
		 @abstract image pixel size.
		 */
		Size2i size() { return m_size; }

		/*!
		 @function
		 @abstract get inner size. <br>
		 On some platfrom, the GL texture must have a (2 pow N) size. This is that extended size. <br>
		 If not use the 2 pow N size. This is equal to `size()`.
		 */
		Size2i inner_size() { return m_inner_size; }

		/*!
		 @function
		 @abstract == (float)size/inner_size.
		 */
		Vec2 const & using_range() { return m_range; };

		ePixel format() { return m_format; }

	public:
		static std::shared_ptr<Texture> Create();

		/*!
		 @function
		 @abstract reset the texture with data. <br>
		 @arg width image width in pixel, also the stride of data.
		 @arg height image height in pixel.
		 @arg bpp pixel type. (must be valid; `None` was not allowed)
		 @arg data the data buffer, must a have valid length (== width * height * (int)bpp).
		 */
		void set(size_t width, size_t height, ePixel bpp, void const * data);

		/*!
		 @function
		 @abstract set content with the image.
		 */
		void set(std::shared_ptr<Image> image);

	protected:
		TextureBuffer m_buffer;
		Size2i m_size;
		Size2i m_inner_size;
		size_t m_height_in = 0;
		Vec2 m_range = { 1, 1 };
		ePixel m_format = ePixel::None;
	};

	typedef std::shared_ptr<Texture> SharedTexture;

	/*!
	 @class
	 @abstract texture loader. load textures with image file path. <br>
	 the ImageDecoder was used to decode image to bitmap.
	 */
	class TextureLoader {
	public:
		static TextureLoader * Get();

	public:

		/*!
		 @function
		 @abstract load texture from the file of path.
		 */
		SharedTexture loadTexture(const std::string & path);

		/*!
		 @function
		 @abstract get the inner texture.
		 */
		SharedTexture getParticleTexture();
		/*!
		 @function
		 @abstract get the inner texture. (a cross)
		 */
		SharedTexture getCrossTexture();
		/*!
		 @function
		 @abstract get the inner texture. (a circle)
		 */
		SharedTexture getCircleTexture();

	public:
		/*!
		@function
		@abstract the image decoder. <br>
		A default decoder(extentsion is '') was set. To use custom decoders, must remove the default one: <br>
		use `clearImageDecoders()` or `setImageDecoder('', nullptr)`.
		@arg extension image extension (such as ".jpg", ".png")
		@arg decoder the image decoder. set nullptr to remove.
		 */
		void setImageDecoder(std::string const & extension, std::shared_ptr<ImageDecoder> decoder);

		/*!
		 @function
		 @abstract get the decoder can be used for path. <br>
		 @return if path has a tail match the 'extension', return that decoder. <br>
		 if not found, return nullptr.
		 */
		std::shared_ptr<ImageDecoder> getImageDecoder(std::string const & path);

		/*!
		 @function
		 @abstract remove all decoders.
		 */
		void clearImageDecoders();

	protected:
		TextureLoader();

		std::map<std::string, std::shared_ptr<ImageDecoder> > m_image_decoders;

	};

	/*!
	 @class
	 @abstract a cache used to store shared textures.
	 */
	class TexturesCache : protected Cache<std::string, SharedTexture>
	{
	public:
		static TexturesCache * Get();

	public: // cache texture
		/*!
		 @function cacheTexture
		 @abstract add texture to cache with key.
		 */
		void cacheTexture(std::string const & path, SharedTexture texture) { cache(path, texture); }

		/**
		 @function getTexture
		 @abstract  get texture with key.
		 @return if found in texture cache, return it. or <br>
		 treat key as a file path, load that file as image,  then push to cache and return it. or <br>
		 if load image failed in case2 or case3, return nullptr. <br>
		 */
		SharedTexture getTexture(std::string const & path) { return load(path); }

		/*!
		 @function clearCachedTextures
		 @abstract remove textures in cache.
		 @arg include_used default = false, just remove unused. case true, remove all.
		 */
		void clearCachedTextures(bool include_used = false) { clearCache(include_used); }

	protected:

		TexturesCache() = default;

	protected:

		std::shared_ptr<Zen::Vap::Texture> onCacheNotFound(const std::basic_string<char> &key) override
		{
			auto texture = TextureLoader::Get()->loadTexture(key);
			if(texture) this->cache(key, texture);
			return texture;
		}
	};
}}
